-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Implementation of URLProtocol and refactoring URLSession #968
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implementation of URLProtocol and refactoring URLSession #968
Conversation
Foundation/NSURLProtocol.swift
Outdated
open class func registerClass(_ protocolClass: AnyClass) -> Bool { | ||
if protocolClass is URLProtocol.Type { | ||
guard !_registeredProtocolClasses.contains(where: { $0 === protocolClass }) else { return true } | ||
_lock.lock() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this needs to go before the guard
otherwise threads can still race?
Foundation/NSURLProtocol.swift
Outdated
} | ||
|
||
// TODO: Synchronize all the class funcs that use the registered protocol classes array | ||
internal class func getProtocolClass(for urlRequest: URLRequest) -> AnyClass? { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Locking needed here too.
Foundation/NSURLProtocol.swift
Outdated
|
||
|
||
private static var _registeredProtocolClasses = [AnyClass]() | ||
private static var _lock = NSLock() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps call this _classesLock
?
TestFoundation/main.swift
Outdated
@@ -80,7 +80,7 @@ XCTMain([ | |||
testCase(TestNSURLResponse.allTests), | |||
testCase(TestNSHTTPURLResponse.allTests), | |||
//Disabling because of https://bugs.swift.org/browse/SR-4655 and https://bugs.swift.org/browse/SR-4647 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment needs to be removed if we are enabling the tests
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we should re-enable the tests under this PR.
} | ||
} | ||
|
||
func urlProtocol(_ protocol: URLProtocol, didFailWithError error: Error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Will it be possible to factor out some code this function shares with urlProtocol(_ protocol: URLProtocol, didFailWithError error: Error)
?
Foundation/NSURLProtocol.swift
Outdated
open class func unregisterClass(_ protocolClass: AnyClass) { NSUnimplemented() } | ||
open class func unregisterClass(_ protocolClass: AnyClass) { | ||
if let idx = _registeredProtocolClasses.index(where: { $0 === protocolClass }) { | ||
_classesLock.lock() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The lock acquisition needs to be raised above the if
, given that _registeredProtocolClasses
may be concurrently accessed
@@ -219,6 +219,11 @@ open class URLSession : NSObject { | |||
let c = URLSession._Configuration(URLSessionConfiguration: configuration) | |||
self._configuration = c | |||
self.multiHandle = _MultiHandle(configuration: c, workQueue: workQueue) | |||
// registering all the protocol classes with URLProtocol | |||
let _ = URLProtocol.registerClass(_HTTPURLProtocol.self) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am wondering if we want to register the native protocol classes
every time a new URLSession is created. This can be a one-time activity.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PR: #1030
@@ -234,6 +239,11 @@ open class URLSession : NSObject { | |||
let c = URLSession._Configuration(URLSessionConfiguration: configuration) | |||
self._configuration = c | |||
self.multiHandle = _MultiHandle(configuration: c, workQueue: workQueue) | |||
// registering all the protocol classes with URLProtocol | |||
let _ = URLProtocol.registerClass(_HTTPURLProtocol.self) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am wondering if we want to register the native protocol classes every time a new URLSession is created. This can be a one-time activity.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PR: #1030
self.easyHandle = _EasyHandle(delegate: self) | ||
if let urlProtocolClass = URLProtocol.getProtocolClass(for: request) { | ||
if urlProtocolClass is URLProtocol.Type { | ||
self._protocol = (urlProtocolClass as! URLProtocol.Type).init(task: self, cachedResponse: nil, client: nil) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Can we separate the downcast on a different line of code ? Though this is fine, I'm for better readability!
@@ -149,14 +117,20 @@ open class URLSessionTask : NSObject, NSCopying { | |||
set { taskAttributesIsolation.async(flags: .barrier) { self._response = newValue } } | |||
} | |||
fileprivate var _response: URLResponse? = nil | |||
internal var setResponse : URLResponse? { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't we call this simply response
?
suspend() | ||
} else { | ||
self.internalState = .transferFailed | ||
completeTask(withError: (self.task?.error)!) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to avoid this unguarded unwrapping
guard let url = request.url else { fatalError("No URL in request.") } | ||
|
||
self.internalState = .transferReady(createTransferState(url: url, workQueue: (task?.workQueue)!)) | ||
if (task?.suspendCount)! < 1 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will it be possible to avoid this unchecked unwrapping?
@nethraravindran Since some new files have been created, I guess you'd need to update the |
Foundation/NSURLProtocol.swift
Outdated
|
||
func urlProtocolDidFinishLoading(_ protocol: URLProtocol) { | ||
guard let session = `protocol`.task?.session as? URLSession else { fatalError() } | ||
switch session.behaviour(for: `protocol`.task!) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
May be we should set a guard for protocol.task
and generate a fatalError
if its is nil? We can then access task in the remainder of the method without worries of nil :)
@swift-ci please test |
@swift-ci test |
@parkera We think this is ready for merge. Can you give your view? Thanks. |
If you're happy with it, go ahead. |
if protocolClass is URLProtocol.Type { | ||
_classesLock.lock() | ||
guard !_registeredProtocolClasses.contains(where: { $0 === protocolClass }) else { | ||
_classesLock.unlock() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd use
defer {
_classesLock.unlock()
}
here and in the following cases, otherwise it looks quite easy that a further refactoring forgets to unlock...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I remembered what @parkera had to say about this :)
#902 (comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
very surprising but fair enough, thanks for pointing that out 👍
Thanks @parkera we're still looking at the |
//set the request timeout | ||
//TODO: the timeout value needs to be reset on every data transfer | ||
|
||
var timeoutInterval = Int(httpSession.configuration.timeoutIntervalForRequest) * 1000 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This timeout logic needs to be moved to URLSessionTask since it is not something specific to HTTP. We can do this in a separate PR though.
fileprivate let body: _Body | ||
fileprivate let tempFileURL: URL | ||
internal var suspendCount = 1 | ||
internal var totalDownloaded = 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is specific to the http download tasks
. We'd need to consider moving it to the HTTP implementation in future.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PR: #1029
Rebased to the latest master and fixed few bugs. Ready for test and merge. |
@swift-ci please test |
cc @nethraravindran build failures due to some compilation errors and warning |
@swift-ci please test |
1 similar comment
@swift-ci please test |
Another TODO: There's a lot of places where we're doing optional chaining. I understand that we will not have nil values at these points under normal circumstances. But for those abnormal nils that may come up once in a while, we have to define clear code paths (may be a guard with Since the tests are passing and the code changes are big, I am fine with merging this now. But it does warrant some future improvement work. Thanks! |
Currently URLSessionTask is tightly coupled with HTTP/HTTPS specific functions. This makes it difficult to introduce support for other native and custom protocols. Refactoring URLSession helps in adding support for other native protocols like ftp and data as well. Also, URLProtocol implementation provides support for custom protocols.
This document provides the proposal for restructuring URLSession
https://gist.github.com/pushkarnk/1bae3167d2e62b09ba0dd4b0dd285179